package godifactroy

import (
	"gitee.com/ichub/goconfig/common/base/basedto"
	"gitee.com/ichub/goconfig/common/base/baseutils/fileutils"
	"gitee.com/ichub/goconfig/common/base/baseutils/jsonutils"
	"gitee.com/ichub/goconfig/common/base/baseutils/stringutils"
	"gitee.com/ichub/goconfig/common/ichublog"
	"gitee.com/ichub/goconfig/godi/internal/codefactroy"
	"gitee.com/ichub/goconfig/godi/internal/didto"
	"github.com/duke-git/lancet/fileutil"
	"github.com/sirupsen/logrus"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"strings"
)

const dataOutputDiStruct = "/data/output/di/struct"
const dataOutputDiStructFile = "/data/output/di/file"

type DiFactroy struct {
	basedto.BaseEntitySingle

	BasePkg string
	Rootdir string

	FileInfoDtos  []*didto.FileinfoDto
	StructInfos   [][]*didto.StructInfo
	StructInfoMap map[string]*didto.StructInfo
}

func NewDiFactroy() *DiFactroy {
	return (&DiFactroy{

		Rootdir:       fileutils.FindRootDir(),
		FileInfoDtos:  make([]*didto.FileinfoDto, 0),
		StructInfoMap: make(map[string]*didto.StructInfo),
	}).init()
}

func (this *DiFactroy) init() *DiFactroy {
	ichublog.InitLogrus()
	this.BasePkg = this.FindBasePkg()
	return this
}

func (this *DiFactroy) MakeDi(dto *didto.DiDto) error {
	return codefactroy.MakeDi(dto)
}

func (this *DiFactroy) MakeBatch(structInfo *didto.StructInfo) bool {

	if !structInfo.IsBaseEntiyStruct {
		return false
	}

	var dto = structInfo.DiDto
	dto.OutPath = filepath.Dir(structInfo.PathFile)
	this.MakeDi(dto)
	return true
}

func (this *DiFactroy) FindBasePkg() string {
	var content, _ = fileutil.ReadFileToString(fileutils.FindRootDirGoMod() + "/go.mod")
	var lines = strings.Split(content, "\n")
	for _, line := range lines {
		if strings.Contains(line, "module") {
			var lineArr = strings.Split(line, "module")
			this.BasePkg = strings.Trim(lineArr[1], " ")
			this.BasePkg = strings.Trim(this.BasePkg, "\r")
			return this.BasePkg
		}

	}
	return ""
}

func (this *DiFactroy) FindFile(file string) *didto.FileinfoDto {
	fset := token.NewFileSet()
	// 这里取绝对路径，方便打印出来的语法树可以转跳到编辑器
	path, _ := filepath.Abs(file)
	_, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
	if err != nil {
		logrus.Println(err)
		return nil
	}
	// 打印语法树 ast.Print(fset, f.Scope.Objects)
	var fi = &didto.FileinfoDto{
		//AstFile:  f,
		PathFile: path,
	}

	return fi
}

func (this *DiFactroy) FindGoFiles() error {

	// 指定需要遍历的目录
	dirPath := this.Rootdir
	// 使用filepath.Walk遍历目录
	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			logrus.Error(err)
			return err
		}
		// 如果是文件，可以进行额外操作，比如读取文件内容
		if !info.IsDir() {
			if !strings.HasSuffix(path, "_test.go") &&
				strings.HasSuffix(path, ".go") {

				var somepath = strings.Split(path, this.Rootdir)
				var some = strings.Replace(path, this.Rootdir, "", -1)
				var file = didto.NewFileInfoDto()
				file.PathFile = this.Rootdir + some
				if len(somepath) > 1 {

					this.FileInfoDtos = append(this.FileInfoDtos, file)
				}
			}
		}
		// 返回nil继续遍历
		return nil
	})

	if err != nil {
		logrus.Error("Error walking the path:", err)
	}
	return err
}

func (this *DiFactroy) ParseDir(pathf string) {

	path, _ := filepath.Abs(pathf)
	fset := token.NewFileSet()
	pkgs, err := parser.ParseDir(fset, path, nil, parser.ParseComments)
	if err != nil {
		logrus.Error(err)
		return
	}
	if pkgs == nil {
		pkgs = map[string]*ast.Package{}
	}
	logrus.Error(path)

}

func (this *DiFactroy) MakeDiAll() {

	this.ParseAll()
	for _, v := range this.FileInfoDtos {
		for _, vv := range v.StructInfos {
			if vv.IsBaseEntiyStruct {
				this.MakeBatch(vv)
			}
		}

	}

}
func (this *DiFactroy) FindSome(struname string) *didto.FileinfoDto {
	for _, v := range this.FileInfoDtos {
		for _, vv := range v.StructInfos {
			if vv.StructName == struname {
				return v
			}
		}
	}
	return nil
}
func (this *DiFactroy) ParseAll() {

	this.FindGoFiles()

	for _, v := range this.FileInfoDtos {
		var fileinfo = this.Parse(v.PathFile)
		this.FileInfoDtos = append(this.FileInfoDtos, fileinfo)

		for _, vv := range fileinfo.StructInfos {
			v.StructInfoMap[vv.PkgName+"::"+vv.StructName] = vv
		}
	}

	this.Pasre2JsonFile()
}
func (this *DiFactroy) Pasre2JsonFile() {
	var path = this.Rootdir + dataOutputDiStruct
	os.Remove(path)
	os.MkdirAll(path, os.ModePerm)

	for _, v := range this.StructInfoMap {
		if !v.IsBaseEntiyStruct {
			continue
		}
		var jsonStr = jsonutils.ToJsonPretty(v)
		var fileName = v.StructName + ".json"
		var filePath = path + "/" + fileName
		fileutil.WriteBytesToFile(filePath, []byte(jsonStr))
		logrus.Info(filePath)
	}

}

func (this *DiFactroy) ParseOneStruct(decl *ast.GenDecl, nodes *ast.File, path string) *didto.StructInfo {
	if decl.Tok != token.TYPE {
		return nil
	}
	if len(decl.Specs) != 1 {
		return nil
	}
	spec, ok2 := decl.Specs[0].(*ast.TypeSpec)
	if !ok2 {
		return nil
	}
	structType, ok3 := spec.Type.(*ast.StructType)
	if !ok3 {
		logrus.Error("失败", structType)
		return nil
	}
	var fields = []string{}
	for _, v := range structType.Fields.List {
		if len(v.Names) > 0 {
			fields = append(fields, v.Names[0].Name)
		} else {
			var expr, ok4 = v.Type.(*ast.SelectorExpr)
			if ok4 {
				fields = append(fields, expr.Sel.Name)
			} else {
				logrus.Info(1)
			}
		}
	}

	structInfo := didto.NewStructInfo()
	structInfo.Fields = fields

	structInfo.PkgName = nodes.Name.Name
	structInfo.PathFile = path
	structInfo.StructName = spec.Name.Name
	structInfo.ParsePkgName(this.Rootdir, this.BasePkg)
	this.StructInfoMap[spec.Name.Name] = structInfo
	structInfo.CheckBaseEntity()
	return structInfo
}

func (this *DiFactroy) ParseFunc(decl *ast.FuncDecl) {
	structName := ""
	switch decl.Recv.List[0].Type.(type) {
	case *ast.StarExpr: //指针方法
		structName = decl.Recv.List[0].Type.(*ast.StarExpr).X.(*ast.Ident).Name
	case *ast.Ident: //普通方法 //
		structName = decl.Recv.List[0].Type.(*ast.Ident).Name
	}
	if structInfo, ok := this.StructInfoMap[structName]; ok {
		structInfo.MethodNames = append(structInfo.MethodNames, decl.Name.Name)
	}

}
func (this *DiFactroy) ExistNewFunc(nodes *ast.File, functionName string) bool {

	var found = false
	for _, obj := range nodes.Scope.Objects {

		decl, ok := obj.Decl.(*ast.FuncDecl)
		if ok {
			if decl.Name.Name == functionName {
				found = true
				if decl.Type.Params != nil && len(decl.Type.Params.List) > 0 {
					found = false
				}
			}
		}
	}
	return found
}

func (this *DiFactroy) findNewFunc(node *ast.File, functionName string) bool {

	found := false
	ast.Inspect(node, func(n ast.Node) bool {
		if fd, ok := n.(*ast.FuncDecl); ok {
			if fd.Name.Name == functionName {
				found = true
				if fd.Type.Params != nil {
					found = false
				}
				return false // 停止遍历
			}
		}
		return true // 继续遍历
	})

	return found
}

func (this *DiFactroy) Parse(file string) *didto.FileinfoDto {

	path, _ := filepath.Abs(file)
	fset := token.NewFileSet()
	nodes, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
	if err != nil {
		logrus.Error(err)
		return nil
	}
	logrus.Info(nodes.Name)
	structInfoList := make([]*didto.StructInfo, 0)
	for i := 0; i < len(nodes.Decls); i++ {

		decl, ok := nodes.Decls[i].(*ast.GenDecl)
		if !ok {
			continue
		}
		var stru = this.ParseOneStruct(decl, nodes, path)
		if stru != nil {
			stru.NewStructName = stru.NewStruMethod()
			stru.ExistNewStruct = this.ExistNewFunc(nodes, stru.NewStructName)
			if !stru.ExistNewStruct {
				stru.NewStructName = stringutils.Lcfirst(stru.NewStruMethod())
				stru.ExistNewStruct = this.ExistNewFunc(nodes, stru.NewStructName)
			}
		}
		if stru != nil {
			structInfoList = append(structInfoList, stru)
		}

	} // 找方法
	for i := 0; i < len(nodes.Decls); i++ {
		decl, ok := nodes.Decls[i].(*ast.FuncDecl)
		if !ok {
			continue
		}
		if decl.Recv == nil || len(decl.Recv.List) != 1 {
			continue
		}
		this.ParseFunc(decl)

	}
	var fileinfo = didto.NewFileInfoDto()

	for k, obj := range nodes.Scope.Objects {
		logrus.Info(k)
		decl, oks := obj.Decl.(*ast.FuncDecl)
		if oks {

			logrus.Info(decl.Name.Name)
			fileinfo.Funcs = append(fileinfo.Funcs, decl.Name.Name)
		}
	}

	fileinfo.StructInfos = structInfoList
	fileinfo.PathFile = path
	// 输出
	logrus.Info(jsonutils.ToJsonPretty(fileinfo))
	return fileinfo

}
